// encoding utf-8
#ifndef __QyNet__
#define __QyNet__

#include <QtCore/QDataStream>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtNetwork/QHostAddress>

class QTcpServer;
class QTcpSocket;

/*! @defgroup QyNet Система обмена сообщениями

Принцип работы прост:
- наследуемся от AbstractDatagramEncoder, определяем функции кодирования и декодирования данных.
Например если нам нужно передавать строку и число в одном "пакете" (дейтаграмме):
\code
// Перечесление типов пакетов
struct MyDatagrammTypes
    enum {
        StrInt
    };
};
class MyStringDatagram : public QyNet::AbstractDatagramEncoder {
public:
    virtual quint32 type() const { return MyDatagrammTypes::StrInt; }
    virtual QByteArray encode() const {

        // поток и массив для данных
        QByteArray data;
        QDataStream ds( &data, QIODevice::WriteOnly );

        // пишем данные в поток
        ds << str;
        ds << number;

        // возвращаем массив
        return data;
    }
    virtual void decode( QByteArray data ) {

        // открываем поток на чтение
        QDataStream ds( &data, QIODevice::ReadOnly );

        // читаем
        ds >> str;
        ds >> number;
    }

    // наши данные
    QString str;
    qint32 number;

};
\endcode
- Далее нужно определить фабрику пакетов, которая будет создавать пакеты когда это потребуется.
Можно воспользоваться QyNetUtils::DatagramEncoderFactory или написать свой фабричный класс.
- Регистрируем фабрику в центре управления упаковкой
\code
// создаем экземпляр нашей фабрики
QyNetUtils::DatagramEncoderFactory<MyStringDatagram> * myFactory
    = new QyNetUtils::DatagramEncoderFactory<MyStringDatagram>();

// регистрируем, теперь если будет получено сообщение с типом MyDatagrammTypes::StrInt, то для его
// декодирования будет использован метод декодирования определенный в MyStringDatagram
QyNet::Kernel::registerFactory(myFactory);
\endcode

\b ВАЖНО: Код написанный выше, должен быть и в серверной часте и в клиентской.

- Создаем экземпляр класса QyNet::Server(для серверной части)/QyNet::Client(для клиентской части)
- Подключаем соответствующие слоты
- запускаем прослушку нужного порта/подключаемся к серверу

*/

//! Пространство имен системы обмена данными
namespace QyNet {

/*! \brief Интерфейс упаковщика данных
	\ingroup QyNet
*/
class AbstractDatagramEncoder {
public:
	virtual ~AbstractDatagramEncoder() {}

	virtual quint32 type() const = 0; //! Тип пакета
	virtual QByteArray encode() const = 0; //! упаковать данные

	virtual void decode( QByteArray data ) = 0; //! Распаковать данные

};


/*! \brief Фабрика упаковщиков
	\ingroup QyNet
*/
class AbstractDatagramEncoderFactory {
public:
	virtual ~AbstractDatagramEncoderFactory() {}

	virtual quint32 type() const = 0; //! Тип упаковщика
	virtual AbstractDatagramEncoder * create() const = 0; //! Создать упаковщик

};


/*! \brief Центр урправления упаковки
	\ingroup QyNet
*/
class Kernel {
	Kernel() {}
	~Kernel() {}

public:
	static void registerFactory( AbstractDatagramEncoderFactory * datagramEncoderFactory ); //! Зарегистрировать новую фабрику кпаковки

	static AbstractDatagramEncoderFactory * unregisterFactory( quint32 type ); //! Разрегистрировать фабрику =)
	static Kernel * instance(); //! Создаст ЦУ если его нету и вернет Указатель на него
	AbstractDatagramEncoder * createEncoder( quint32 type ) const; //! Создать упаковщик заданного типа

private:
	Q_DISABLE_COPY(Kernel);

	QMap<quint32,AbstractDatagramEncoderFactory*> p_factories;

};


/*! \brief Запись о соединении
	\ingroup QyNet
*/
class ConnectionRecord : public QObject {
	Q_OBJECT
public:
	explicit ConnectionRecord( QTcpSocket * socket, QObject * parent = 0 );
	virtual ~ConnectionRecord() {}

	QTcpSocket * socket() const { return p_socket; } //! Указатель на сокет

public slots:
	void send( QyNet::AbstractDatagramEncoder * datagram ); //! Отправить данные
    void sendSpeedTest(); //!< Запустить тест скорости передачи данных

signals:
	void datagramReceived( QyNet::AbstractDatagramEncoder * datagram ); //! Получены данные
	void disconnected(); //! Отсоединен
	void errorMessage(); //! произошла ошибка
    void speedTestComplete( qreal speed ); //!< кол-во мсекунд, для пересылки 1МБ полезных данных

private slots:
	// void on_socket_connected();
	// void on_socket_disconnected();
    void on_socket_error( QAbstractSocket::SocketError socketError );
	// void on_socket_hostFound();
	// void on_socket_proxyAuthenticationRequired ( const QNetworkProxy & proxy, QAuthenticator * authenticator );
	// void on_socket_stateChanged( QAbstractSocket::SocketState socketState );
	void on_socket_readyRead();

private:
    void sendSpeedTest( quint8 flag, quint32 time );

	QTcpSocket * p_socket;
	qint64 currentSize;

};


/*! \brief Сервер
	\ingroup QyNet
*/
class Server : public QObject {
	Q_OBJECT
public:
	Server( QObject * parent = 0 ) : QObject(parent) { init(); }
	explicit Server( quint16 port, const QHostAddress & address = QHostAddress::Any,
		QObject * parent = 0 ) : QObject(parent) { init(); start( port, address ); }
	virtual ~Server() {}

	QTcpServer * server() const { return sv_socket; } //! Сокет
	Q_INVOKABLE bool start( quint16 port, const QHostAddress & address = QHostAddress::Any ); //! Запустить сервер
	QList<ConnectionRecord*> connections(); //! Список актуальных соединений

public slots:
	void sendAll( QyNet::AbstractDatagramEncoder * datagram ); //! Отослать всем клиентам данные
	void sendAll( const QList<QyNet::AbstractDatagramEncoder*> & datagrams ); //! Отослать всем клиентам список даннх
	void stop(); //! Остановить сервер

signals:
	void newConnection( QyNet::ConnectionRecord * connectionRecord ); //! Новое подключение
	void connectionClosed( QyNet::ConnectionRecord * connectionRecord ); //! Подключение закрыто

protected:
	virtual QyNet::ConnectionRecord * createConnectionRecord( QTcpSocket * socket ); //! Создать запись о соединении

private slots:
	void on_serverSocket_newConnection();
	void disconnected();

private:
	void init();

	QTcpServer * sv_socket;
	QMutex connectionRecordsProtector;
	QList<ConnectionRecord*> connectionRecords;

};


/*! \brief Клиент
	\ingroup QyNet
*/
class Client : public ConnectionRecord {
	Q_OBJECT
public:
	Client( QObject * parent = 0 );
	explicit Client( const QHostAddress & address, quint16 port, QObject * parent = 0 );

	void connectToHost( const QHostAddress & address, quint16 port ); //! Подключиться к серверу
    void disconnectFromHost();

};

};


namespace QyNetUtils {

/*! \brief Шаблон фабрики упаковщиков
	\ingroup QyNet
*/
template < class DatagramEncoderType >
class DatagramEncoderFactory : public QyNet::AbstractDatagramEncoderFactory {
public:
	explicit DatagramEncoderFactory( quint32 type ) : p_type(type) {}
	virtual ~DatagramEncoderFactory() {}

	virtual quint32 type() const { return p_type; }
	virtual DatagramEncoderType * create() const { return new DatagramEncoderType; }

private:
	quint32 p_type;

};

//! Шаблон функции упаковки любого типа, для типа должны быть определены потоковые операторы для QDataStream
template < typename Type >
inline QByteArray pack( const Type & type ) {

	QByteArray data;
	QDataStream ds( &data, QIODevice::WriteOnly );
    ds.setVersion(QDataStream::Qt_4_0);
	ds << type;

	return data;
}

//! Шаблон функции распаковки любого типа, для типа должны быть определены потоковые операторы QDataStream
template < typename Type >
inline Type unpack( QByteArray data ) {

	Type type;
	QDataStream ds( &data, QIODevice::ReadOnly );
    ds.setVersion(QDataStream::Qt_4_0);
	ds >> type;

	return type;
}

};

#endif//__QyNet__
